#ifdef _WIN32
#include <windows.h>
#endif

#include "GLee.h"
#include <GL/glu.h>

#include "TexturedEntity.h"
#include "glslshader.h"
#include "TextureManager.h"
#include "Behaviour.h"
#include "Animation.h"
#include "GLTexture.h"

GLSLProgram* TexturedEntity::m_shaderProgram = NULL;

TexturedEntity::TexturedEntity(void)
: Entity(), m_textureName(), m_texture(), m_vertexBuffer(0), m_texCoordBuffer(0), 
m_positionInTexture(), m_sizeInTexture(), m_behaviour(NULL), m_animation(NULL)
{
}

TexturedEntity::TexturedEntity(boost::shared_ptr<Scene> scene)
: Entity(scene), m_textureName(), m_texture(), m_vertexBuffer(0), m_texCoordBuffer(0), 
m_positionInTexture(), m_sizeInTexture(), m_behaviour(NULL), m_animation(NULL)
{
}

TexturedEntity::TexturedEntity(boost::shared_ptr<Scene> scene, const std::string& textureName)
: Entity(scene), m_textureName(textureName), m_texture(), m_vertexBuffer(0), m_texCoordBuffer(0), 
m_positionInTexture(), m_sizeInTexture(), m_behaviour(NULL), m_animation(NULL)
{
}

TexturedEntity::~TexturedEntity(void)
{
}

void TexturedEntity::updatePixelsSize(const Dimension& size)
{
    m_pixelsSize = size;

    glBindBuffer(GL_ARRAY_BUFFER, m_vertexBuffer);
    GLfloat vertex [] = {  0.0f,  0.0f, //First Square vertex
                           m_pixelsSize.width,  0.0f,
                           m_pixelsSize.width, m_pixelsSize.height,
                           0.0f, m_pixelsSize.height  //Last Square vertex
    };
    glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(float) * 8, &vertex[0]);
}

void TexturedEntity::attachBehaviour(Behaviour* behaviour) 
{ 
    if (m_behaviour != NULL)
    {
        delete m_behaviour;
    }
    m_behaviour = behaviour; 
}

void TexturedEntity::attachAnimation(Animation* animation) 
{ 
    if (m_animation != NULL)
    {
        delete m_animation;
    }
    m_animation = animation; 
}

void TexturedEntity::detachBehaviour(void)
{
    if (m_behaviour != NULL)
    {
        delete m_behaviour;
    }
    m_behaviour = NULL; 
}

void TexturedEntity::detachAnimation(void)
{
    if (m_animation != NULL)
    {
        delete m_animation;
    }
    m_animation = NULL; 
}

bool TexturedEntity::commonInit()
{
    // Load texture file
    TextureManager* manager = TextureManager::getInstance();
    if (manager->hasTexture(m_textureName))
    {
        m_texture = manager->getTexture(m_textureName);
    }
    else
    {
        m_texture = boost::shared_ptr<GLTexture>(new GLTexture(m_textureName, GL_LINEAR, 
                                  GL_LINEAR_MIPMAP_LINEAR, GL_RGB8, GL_RGB,
                                  GL_UNSIGNED_BYTE, true));
        manager->addTexture(m_texture);
    }

    GLfloat vertex [] = {  0.0f,  0.0f, //First Square vertex
                           m_pixelsSize.width,  0.0f,
                           m_pixelsSize.width, m_pixelsSize.height,
                           0.0f, m_pixelsSize.height  //Last Square vertex
    };

    glGenBuffers(1, &m_vertexBuffer); //Generate a buffer for the vertices
    glBindBuffer(GL_ARRAY_BUFFER, m_vertexBuffer); //Bind the vertex buffer
    glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * 8, &vertex[0], GL_STATIC_DRAW); //Send the data to OpenGL

    GLfloat texCoords [] = { 
        m_positionInTexture.x / m_texture->width(), m_positionInTexture.y / m_texture->height(), 
        (m_positionInTexture.x + m_sizeInTexture.width) / m_texture->width(), m_positionInTexture.y / m_texture->height(), 
        (m_positionInTexture.x + m_sizeInTexture.width) / m_texture->width(), (m_positionInTexture.y + m_sizeInTexture.height) / m_texture->height(), 
        m_positionInTexture.x / m_texture->width(), (m_positionInTexture.y + m_sizeInTexture.height) / m_texture->height()
    };

    glGenBuffers(1, &m_texCoordBuffer); //Generate a buffer for the vertices
    glBindBuffer(GL_ARRAY_BUFFER, m_texCoordBuffer); //Bind the vertex buffer
    glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * 8, &texCoords[0], GL_STATIC_DRAW); //Send the data to OpenGL

    if (m_shaderProgram == NULL)
    {
        m_shaderProgram = new GLSLProgram("data/shaders/simple-texture.vert", 
                                            "data/shaders/simple-texture.frag");

        if (!m_shaderProgram->initialize())
        {
            std::cerr << "Could not initialize the explosion shaders" << std::endl;
            return false;
        }

        m_shaderProgram->bindAttrib(0, "a_Vertex");
        m_shaderProgram->bindAttrib(1, "a_TexCoord0");
        m_shaderProgram->linkProgram();
    }

    return true;
}

void TexturedEntity::commonPrepare(float dT)
{
    if (m_behaviour != NULL)
    {
        m_behaviour->prepare(dT);
    }
    if (m_animation != NULL)
    {
        m_animation->update(dT);
    }
}

bool TexturedEntity::onInitialize()
{
    return commonInit();
}

void TexturedEntity::onPrepare(float dT)
{
    commonPrepare(dT);
}

void TexturedEntity::onRender() const
{
    glDisable(GL_CULL_FACE);
    glDisable(GL_DEPTH_TEST);

    m_shaderProgram->bindShader();
    m_shaderProgram->sendUniform("texture0", 0);

    glBindTexture(GL_TEXTURE_2D, m_texture->textureID());

    glEnableVertexAttribArray(0);
    glEnableVertexAttribArray(1);

    glBindBuffer(GL_ARRAY_BUFFER, m_vertexBuffer);
    glVertexAttribPointer((GLint)0, 2, GL_FLOAT, GL_FALSE, 0, 0);

    glBindBuffer(GL_ARRAY_BUFFER, m_texCoordBuffer);
    if (m_animation != NULL)
    {
        std::vector<float> texCoords = m_animation->getTexCoord(Dimension(
            static_cast<float> (m_texture->width()), 
            static_cast<float> (m_texture->height())));
        glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(float) * 8, &texCoords[0]);
    }
    glVertexAttribPointer((GLint)1, 2, GL_FLOAT, GL_FALSE, 0, 0);

    // Perform basic transformations 
    glPushMatrix();
    glTranslatef(m_position.x, 
                 m_position.y, 
                 m_position.z);   


    glPushMatrix();

    // Apply rotation if any
    applyRotation();

    glTranslatef(-1.0f * m_pixelsSize.width / 2.0f, 
                 -1.0f * m_pixelsSize.height / 2.0f, 
                 0.0f);

    // Obtain the modelview and the projection matrix
    static float modelviewMatrix[16];
    static float projectionMatrix[16];
    glGetFloatv(GL_MODELVIEW_MATRIX, modelviewMatrix);
    glGetFloatv(GL_PROJECTION_MATRIX, projectionMatrix);

    m_shaderProgram->sendUniform4x4("modelview_matrix", modelviewMatrix);
    m_shaderProgram->sendUniform4x4("projection_matrix", projectionMatrix);

    // Render the simple geometry
    glDrawArrays(GL_QUADS, 0, 4);

    glPopMatrix();

    glDisableVertexAttribArray(0);
    glDisableVertexAttribArray(1);

    glPopMatrix();

    glEnable(GL_DEPTH_TEST);
    glEnable(GL_CULL_FACE);
}

void TexturedEntity::onPostRender()
{
}

void TexturedEntity::onShutdown()
{
}

void TexturedEntity::onDestroy(void)
{
}

